package com.hjl.sr.security;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.hjl.sr.codec.DigestUtils;
import com.hjl.sr.remoting.EncryptContext;
import com.hjl.sr.util.IoUtil;
import com.hjl.sr.util.StringUtil;

public class BlowFishEncrypter implements Encrypter {
	
	private static final String CIPHER_NAME = "Blowfish/CFB8/NoPadding";
	
	private static final String KEY_SPEC_NAME = "Blowfish";
	
	private String key;
	
	public BlowFishEncrypter(String key) {
		super();
		this.key = key;
	}

	private static final ThreadLocal<HashMap<String, CipherPair>> pool = new ThreadLocal<HashMap<String, CipherPair>>();
	
	public static CipherPair createCipher(String key) {
		try {
			String iv = StringUtil.substring(DigestUtils.md5Hex(key), 0, 8);
			String secret =  StringUtil.substring(DigestUtils.md5Hex(key), 0, 16);
			IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes());
			SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), KEY_SPEC_NAME);
			
			Cipher encoder = Cipher.getInstance(CIPHER_NAME);
			Cipher decoder = Cipher.getInstance(CIPHER_NAME);
			encoder.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
			decoder.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
			return new CipherPair(encoder, decoder);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	static class CipherPair {
		private Cipher encoder;
		private Cipher decoder;
		public CipherPair(Cipher encoder, Cipher decoder) {
			super();
			this.encoder = encoder;
			this.decoder = decoder;
		}
		public Cipher getEncoder() {
			return encoder;
		}
		public void setEncoder(Cipher encoder) {
			this.encoder = encoder;
		}
		public Cipher getDecoder() {
			return decoder;
		}
		public void setDecoder(Cipher decoder) {
			this.decoder = decoder;
		}
	}
	
	protected CipherPair getCipher() {
		HashMap<String, CipherPair> keyMap = pool.get();
		if (keyMap == null) {
			keyMap = new HashMap<String, CipherPair>();
			pool.set(keyMap);
		}
		CipherPair instance = (CipherPair) keyMap.get(key);
		if (instance == null) {
			instance = createCipher(key);
			keyMap.put(key, instance);
		}
		return instance;
	}
	
	protected void removeCipher() {
		pool.get().remove(key);
	}
	
	
	public byte[] decryptBytes(byte[] input) {
		try {
			return getCipher().getDecoder().doFinal(input);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} 
	}

	public InputStream decryptInputStream(InputStream inputStream, EncryptContext encryptContext) {
		try {
			Cipher cipher = getCipher().getDecoder();
			CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
			// 复制bytes的原因是cipher线程不安全，当cipherInputStream中的数据未读完的时候会发生混乱			
			ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
			IoUtil.copy(cipherInputStream, bos);
			return new ByteArrayInputStream(bos.toByteArray());
		} catch (IOException e) {
			// io异常的时候抛弃当前的cipher
			removeCipher();
			throw new RuntimeException(e);
		}
	}

	public byte[] encryptBytes(byte[] input) {
		try {
			return getCipher().getEncoder().doFinal(input);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} 
	}

	public OutputStream encryptOutputStream(OutputStream outputStream, EncryptContext encryptContext) {
		Cipher cipher = getCipher().getEncoder();
		CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
		return cipherOutputStream;
	}

}
